# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from twisted.enterprise import adbapi
import pymysql
from docx import Document


class BqgnovelPipeline:
    def __init__(self, mysql_config):
        # 初始化数据库连接池
        self.dbpool = adbapi.ConnectionPool(
            mysql_config['DRIVER'],  # 数据库驱动
            host=mysql_config['HOST'],  # 数据库主机
            user=mysql_config['USER'],  # 数据库用户名
            password=mysql_config['PASSWORD'],  # 数据库密码
            database=mysql_config['DATABASE'],  # 数据库名称
            auth_plugin=mysql_config['auth_plugin'],  # 认证插件
            charset='utf8'  # 数据库字符集
        )
        # 缓存item的列表
        self.item_buffer = []
        # 批量插入的阈值（积累10条数据就插入一次）
        self.buffer_threshold = 10

    @classmethod
    # 只要重写了该方法， 那么以后创建对象时，就会调用该方法获取pipeline对象
    def from_crawler(cls, crawler):
        mysql_config = crawler.settings['MYSQL_DB_CONFIG']  # 读取crawler中的mysql配置
        return cls(mysql_config)  # 返回pipeline对象

    # 每个项目管道组件必须调用此方法
    def process_item(self, item, spider):
        # 将item加入缓存列表
        self.item_buffer.append(item)

        # 当缓存数量达到阈值时，执行批量插入
        if len(self.item_buffer) >= self.buffer_threshold:
            self.batch_insert()

        return item

    # 排序并插入数据
    def batch_insert(self):
        # 1. 按chapter_num排序缓存的item
        sorted_items = sorted(self.item_buffer, key=lambda x: x['chapter_num'])

        # 2. 执行批量插入
        # 调用具体插入SQL的方法
        result1 = self.dbpool.runInteraction(self.insert_sql, sorted_items)
        # 如果回执报错调用插入错误方法
        result1.addErrback(self.insert_error)

        # 3. 清空缓存
        self.item_buffer = []

        # 4. 将数据导出到Word文档
        result2 = self.dbpool.runInteraction(self.write_word)
        result2.addErrback(self.insert_error)

    # 具体插入SQL方法
    def insert_sql(self, cursor, sorted_items):
        # 定义插入语句
        sql = '''
        insert into bqgSexy (novel_name, title, content, chapter_url, chapter_num) 
        values (%s, %s, %s, %s, %s)
        '''

        # 准备批量插入的参数列表
        args_list = [
            (item['novel_name'], item['title'], item['content'], item['chapter_url'], item['chapter_num'])
            for item in sorted_items
        ]
        # 执行批量插入
        cursor.executemany(sql, args_list)

    # 具体查询SQL方法
    def write_word(self, cursor):
        # 查询所有小说名（去重）
        novel_name_sql = 'SELECT DISTINCT novel_name FROM bqgSexy;'
        cursor.execute(novel_name_sql)
        novel_name_results = cursor.fetchall()  # 获取所有查询结果，结果是元组列表（如 [('小说A',), ('小说B',)]）

        # 根据小说名查询对应章节名及内容
        for novel_name_tuple in novel_name_results:
            # 从元组中提取小说名（fetchall() 返回的每个结果是元组，即使只有一个字段）
            novel_name = novel_name_tuple[0]

            # 使用参数化查询，避免 SQL 注入，同时规范格式
            sql = """
                    SELECT title, content 
                    FROM bqgSexy 
                    WHERE novel_name = %s 
                    ORDER BY chapter_num ASC;
                """
            # 执行查询时通过参数传递小说名，而非字符串拼接
            cursor.execute(sql, (novel_name,))  # 注意参数需用元组形式传递，即使只有一个参数
            results = cursor.fetchall()  # 获取所有查询结果

            # 创建Word文档并写入内容
            doc = Document()
            # 添加小说名作为文档标题（可选）
            doc.add_heading(novel_name, level=0)  # level=0是最高级标题
            # 写入每一章的内容
            for title, content in results:
                doc.add_heading(title, level=1)  # 将标题作为一级标题插入
                doc.add_paragraph(content)  # 插入内容段落（自动换行）
                doc.add_page_break()  # 每章结束后添加分页（可选，让章节分页显示）

            # 保存文档，文件名使用小说名
            # 处理文件名中的特殊字符（防止保存失败）
            safe_filename = "".join([c for c in novel_name if c not in '/\:*?"<>|']) + ".docx"
            doc.save(safe_filename)
            print(f"已生成文档：{safe_filename}")

    # 插入错误方法
    def insert_error(self, failure):
        # 打印分隔线
        print('===================================')
        # 打印错误信息
        print(failure)
        # 打印分隔线
        print('===================================')

    # 爬虫结束时处理剩余缓存的item
    def close_spider(self, cursor):
        if self.item_buffer:  # 如果还有未插入的数据
            self.batch_insert()

        # 关闭数据库连接
        cursor.close()
        self.dbpool.close()